/**
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.jimm.protocols.icq.core;

import java.io.IOException;
import java.util.List;
import java.util.Vector;

import android.util.Log;

import org.jimm.protocols.icq.Flap;
import org.jimm.protocols.icq.Tlv;
import org.jimm.protocols.icq.integration.listeners.ContactListListener;
import org.jimm.protocols.icq.integration.listeners.MessagingListener;
import org.jimm.protocols.icq.integration.listeners.MetaAckListener;
import org.jimm.protocols.icq.integration.listeners.MetaInfoListener;
import org.jimm.protocols.icq.integration.listeners.OurStatusListener;
import org.jimm.protocols.icq.integration.listeners.UserStatusListener;
import org.jimm.protocols.icq.integration.listeners.XStatusListener;
import org.jimm.protocols.icq.request.Request;
import org.jimm.protocols.icq.request.RequestKeeper;
import org.jimm.protocols.icq.request.event.RequestListener;
import org.jimm.protocols.icq.setting.Tweaker;

/**
 * <p>
 * Created by
 * 
 * @author Fabrice Michellonet
 * @author Samolisov Pavel
 */
public class OscarConnection implements OscarClient.Listener {

	private static final String LOG_TAG = "ICQ:OscarConnection";

	private Tlv cookie;
	private String userId;
	private String password;
	private boolean authorized = false;

	private Tweaker tweaker;
	private OscarClient client;
	private OscarPacketAnalyser analyser;
	private RequestKeeper requestKeeper;
	private List<MessagingListener> messagingListeners;
	private List<UserStatusListener> userStatusListeners;
	private List<OurStatusListener> ourStatusListeners;
	private List<XStatusListener> xStatusListeners;
	private List<ContactListListener> contactListListeners;
	private List<MetaInfoListener> metaInfoListeners;
	private List<MetaAckListener> metaAckListeners;

	private int flapSeqNrs;

	public OscarConnection(String host, int port, String userId, String password) {
		this(host, port, userId, password, new Tweaker());
	}

	public OscarConnection(String host, int port, String userId,
			String password, Tweaker tweaker) {
		this.userId = userId;
		this.password = password;
		this.tweaker = tweaker;
		analyser = new OscarPacketAnalyser(this);
		client = new OscarClient(host, port, analyser, this);
		requestKeeper = new RequestKeeper();
		messagingListeners = new Vector<MessagingListener>();
		ourStatusListeners = new Vector<OurStatusListener>();
		userStatusListeners = new Vector<UserStatusListener>();
		xStatusListeners = new Vector<XStatusListener>();
		contactListListeners = new Vector<ContactListListener>();
		metaInfoListeners = new Vector<MetaInfoListener>();
		metaAckListeners = new Vector<MetaAckListener>();
	}

	public void addMetaAckListener(MetaAckListener listener) {
		Log.d(LOG_TAG, "MetaAckListener " + listener.getClass().getName()
				+ " has been added");
		metaAckListeners.add(listener);
	}

	public boolean removeMetaAckListener(MetaAckListener listener) {
		Log.d(LOG_TAG, "MetaAckListener " + listener.getClass().getName()
				+ " has been removed");
		return metaAckListeners.remove(listener);
	}

	public void addMetaInfoListener(MetaInfoListener listener) {
		Log.d(LOG_TAG, "MetaInfoListener " + listener.getClass().getName()
				+ " has been added");
		metaInfoListeners.add(listener);
	}

	public boolean removeMetaInfoListener(MetaInfoListener listener) {
		Log.d(LOG_TAG, "MetaInfoListener " + listener.getClass().getName()
				+ " has been removed");
		return metaInfoListeners.remove(listener);
	}

	public void addContactListListener(ContactListListener listener) {
		Log.d(LOG_TAG, "ContactListListener " + listener.getClass().getName()
				+ " has been added");
		contactListListeners.add(listener);
	}

	public boolean removeContactListListener(ContactListListener listener) {
		Log.d(LOG_TAG, "ContactListListener " + listener.getClass().getName()
				+ " has been removed");
		return contactListListeners.remove(listener);
	}

	public void addMessagingListener(MessagingListener listener) {
		Log.d(LOG_TAG, "MessagingListener " + listener.getClass().getName()
				+ " has been added");
		messagingListeners.add(listener);
	}

	public boolean removeMessagingListener(MessagingListener listener) {
		Log.d(LOG_TAG, "MessagingListener " + listener.getClass().getName()
				+ " has been removed");
		return messagingListeners.remove(listener);
	}

	public void addUserStatusListener(UserStatusListener listener) {
		Log.d(LOG_TAG, "UserStatusListener " + listener.getClass().getName()
				+ " has been added");
		userStatusListeners.add(listener);
	}

	public boolean removeUserStatusListener(UserStatusListener listener) {
		Log.d(LOG_TAG, "UserStatusListener " + listener.getClass().getName()
				+ " has been removed");
		return userStatusListeners.remove(listener);
	}

	public void addXStatusListener(XStatusListener listener) {
		Log.d(LOG_TAG, "XStatusListener " + listener.getClass().getName()
				+ " has been added");
		xStatusListeners.add(listener);
	}

	public boolean removeXStatusListener(XStatusListener listener) {
		Log.d(LOG_TAG, "XStatusListener " + listener.getClass().getName()
				+ " has been removed");
		return xStatusListeners.remove(listener);
	}

	public void addOurStatusListener(OurStatusListener listener) {
		Log.d(LOG_TAG, "OurStatusListener " + listener.getClass().getName()
				+ " has been added");
		ourStatusListeners.add(listener);
	}

	public boolean removeOurStatusListener(OurStatusListener listener) {
		Log.d(LOG_TAG, "OurStatusListener " + listener.getClass().getName()
				+ " has been removed");
		return ourStatusListeners.remove(listener);
	}

	/**
	 * Send a packet to the server.
	 * 
	 * @param flapPacket
	 *            The paquet to be sent.
	 */
	public void sendFlap(Flap flapPacket) {
		if (flapPacket.getSequenceNumber() == Integer.MAX_VALUE) { // not
																	// assigned
																	// yet..
			flapSeqNrs++;

			if (flapSeqNrs > 0xffff)
				flapSeqNrs = 0;

			flapPacket.setSequenceNumber(flapSeqNrs);
		}

		client.sendPacket(flapPacket.getByteArray());
	}

	protected synchronized void notifyOnLogout() {
		Log.d(LOG_TAG, "OnLogout");
		for (OurStatusListener l : ourStatusListeners)
			l.onLogout();
	}
	
	protected void notifyOnConnectionError(String errorDescription) {
		Log.d(LOG_TAG, "OnConnectionError (" + errorDescription + ")");
		for (OurStatusListener l : ourStatusListeners)
			l.onConnectionError(errorDescription);
	}

	/**
	 * Send a packet to the server and start the monitoring system.<br/>
	 * The <b>listener</b> will be warned of the server reply by a
	 * <b>RequestAnswerEvent</b> event.
	 * 
	 * @param flapPacket
	 *            The paquet to be sent.
	 * @param listener
	 *            The class that monitor the packet.
	 * 
	 * @return The request object that has been created, null if the flap do not
	 *         contains a Snac section.
	 */
	public synchronized Request sendMonitoredFlap(Flap flapPacket,
			RequestListener listener) {
		int requestId;
		Request request = null;

		if (flapPacket.hasSnac()) {
			requestId = requestKeeper.nextAvailableRequestId();
			flapPacket.getSnac().setRequestId(requestId);

			request = new Request(flapPacket, listener);
			requestKeeper.addRequest(request);
		}
		sendFlap(flapPacket);

		return request;
	}

	/**
	 * Connect to the server
	 */
	public synchronized void connect() {
		try {
			flapSeqNrs = 0;
			cookie = null;

			analyser.initialize();
			// connect to the server
			client.connect();

			Log.d(LOG_TAG, "OscarConnection has been connected");
		} catch (IOException e) {
			Log.e(LOG_TAG, e.getLocalizedMessage(), e);
			notifyOnConnectionError(e.getLocalizedMessage());
		}
	}

	/**
	 * This will cause the connection to be closed.
	 * 
	 * @throws IOException
	 */
	public synchronized void close() {
		client.disconnect();
	}

	protected synchronized void setAuthorized(boolean status) {
		authorized = status;
		if (authorized) {
			for (int i = 0; i < getOurStatusListeners().size(); i++) {
				OurStatusListener l = (OurStatusListener) getOurStatusListeners()
						.get(i);
				Log.d(LOG_TAG, "notify listener " + l.getClass().getName()
						+ " onLogin()");
				l.onLogin();
			}
		}
	}

	public boolean isAuthorized() {
		return authorized;
	}

	public String getUserId() {
		return userId;
	}

	public String getPassword() {
		return password;
	}

	public Tlv getCookie() {
		return cookie;
	}

	public void setCookie(Tlv cookie) {
		this.cookie = cookie;
	}

	public OscarClient getClient() {
		return client;
	}

	public void setClient(OscarClient client) {
		this.client = client;
	}

	public final Tweaker getTweaker() {
		return tweaker;
	}

	public OscarPacketAnalyser getPacketAnalyser() {
		return analyser;
	}

	public List<MessagingListener> getMessagingListeners() {
		return messagingListeners;
	}

	public List<OurStatusListener> getOurStatusListeners() {
		return ourStatusListeners;
	}

	public List<UserStatusListener> getUserStatusListeners() {
		return userStatusListeners;
	}

	public List<XStatusListener> getXStatusListeners() {
		return xStatusListeners;
	}

	public List<ContactListListener> getContactListListeners() {
		return contactListListeners;
	}

	public List<MetaInfoListener> getMetaInfoListeners() {
		return metaInfoListeners;
	}

	public List<MetaAckListener> getMetaAckListeners() {
		return metaAckListeners;
	}

	public RequestKeeper getRequestKeeper() {
		return requestKeeper;
	}

	// OscarClient.Listener
	
	@Override
	public void onConnectionError(String errorDescription) {
		notifyOnConnectionError(errorDescription);
	}

	@Override
	public void onLogout() {
		notifyOnLogout();
	}
}
